1. 参考链接
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (基础)
http://liubin.org/promises-book/ (开源 Promise 迷你书)
http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/ (进阶)
https://promisesaplus.com/ (官方定义规范)
本文大部分都摘抄总结于JavaScript Promise迷你书 ,侵删致歉。
2. 概念 Promise是抽象异步处理对象以及对其进行各种操作的组件。
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
A Promise is in one of these states:
pending : initial state, neither fulfilled nor rejected.fulfilled : meaning that the operation completed successfully.rejected : meaning that the operation failed.
回调函数的异步处理 Node.js规定在JavaScript的回调函数的第一个参数为 Error 对象。
1 2 3 4 5 6 7 8 9 10 使用了回调函数的异步处理 ---- getAsync("fileA.txt" , function (error, result ) { if (error){ throw error; } }); ---- <1 > 传给回调函数的参数为(error对象, 执行结果)组合
通过async方法重写 promise 链
1 2 3 4 5 6 7 8 9 10 11 12 function getProcessedData (url ) { return downloadData(url) .catch(e => { return downloadFallbackData(url) .then(v => { return processDataInWorker(v); }); }) .then(v => { return processDataInWorker(v); }); }
1 2 3 4 5 6 7 8 9 async function getProcessedData (url ) { let v; try { v = await downloadData(url); } catch (e) { v = await downloadFallbackData(url); } return processDataInWorker(v); }
Promise进行异步处理 Promise把类似的异步处理对象和处理规则进行规范化 , 并按照采用统一的接口来编写,而采取规定方法之外的写法都会出错。
1 2 3 4 5 6 7 8 9 10 下面是使用了Promise 进行异步处理的一个例子 ---- var promise = getAsyncPromise("fileA.txt" );promise.then(function (result ) { }).catch(function (error ) { }); ---- <1 > 返回promise对象
Constructor 1 2 3 4 var promise = new Promise (function (resolve, reject ) { });
Instance Method 1 promise.then(onFulfilled, onRejected)
resolve
(成功)时onFulfilled
会被调用
reject
(失败)时onRejected
会被调用
只想对异常进行处理时1 promise.catch(onRejected)
Promise workflow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function asyncFunction ( ) { return new Promise (function (resolve, reject ) { setTimeout(function ( ) { resolve('Async Hello world' ); }, 16 ); }); } asyncFunction().then(function (value ) { console .log(value); }).catch(function (error ) { console .log(error); });
asyncFunction
这个函数会返回promise
对象, 对于这个promise
对象,我们调用它的 then
方法来设置resolve
后的回调函数, catch
方法来设置发生错误时的回调函数。
该promise
对象会在setTimeout
之后的16ms时被resolve
, 这时 then
的回调函数会被调用,并输出'Async Hello world'
。
3. 创建Promise对象 创建XHR的promise对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function getURL (URL ) { return new Promise (function (resolve, reject ) { var req = new XMLHttpRequest(); req.open('GET' , URL, true ); req.onload = function ( ) { if (req.status === 200 ) { resolve(req.responseText); } else { reject(new Error (req.statusText)); } }; req.onerror = function ( ) { reject(new Error (req.statusText)); }; req.send(); }); } var URL = "http://httpbin.org/get" ;getURL(URL).then(function onFulfilled (value ) { console .log(value); }).catch(function onRejected (error ) { console .error(error); });
4. Promise方法 1. Promise.resolve Promise.resolve(value)
可以认为是 new Promise()
方法的快捷方式。
1 2 3 4 5 Promise .resolve(42 )---- new Promise (function (resolve ) { resolve(42 ); });
2. Promise.reject Promise.reject(error)
也是 new Promise()
方法的快捷方式。
1 2 3 4 5 Promise .reject(new Error ("出错了" ))---- new Promise (function (resolve,reject ) { reject(new Error ("出错了" )); });
使用1 2 3 Promise .reject(new Error ("BOOM!" )).catch(function (error ) { console .error(error); });
使用 reject
会比使用 throw
安全。 因为我们很难区分throw
是我们主动抛出来的,还是因为真正的其它异常导致的。
Chrome的开发者工具提供了在程序发生异常的时候自动在调试器中break
的功能。当我们开启这个功能的时候,在执行到下面代码中的 throw
时就会触发调试器的break
行为。
1 2 3 var promise = new Promise (function (resolve, reject ) { throw new Error ("message" ); });
resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,promise 状态一旦改变则不能再变。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。
在then中进行reject 1 2 3 4 5 6 7 var promise = Promise .resolve();promise.then(function ( ) { var retPromise = new Promise (function (resolve, reject ) { }); return retPromise; }).then(onFulfilled, onRejected);
1 2 3 4 5 var onRejected = console .error.bind(console );var promise = Promise .resolve();promise.then(function ( ) { return Promise .reject(new Error ("this promise is rejected" )); }).catch(onRejected);
3. Thenable
类Promise对象。 拥有名为.then方法的对象。
jQuery.ajax()
的返回值是一个具有 .then
方法的 jqXHR Object
对象,就是thenable
的:
$.ajax('/json/comment.json');// => 拥有 `.then` 方法的对象
Promise.resolve
方法将 thenable
对象转换为promise对象。
1 2 3 4 var promise = Promise .resolve($.ajax('/json/comment.json' ));promise.then(function (value ) { console .log(value); });
但即使一个对象具有 .then
方法,也不一定就能作为ES6 Promises对象使用。
4. Promise#then#catch promise方法链(promise chain
)
then : 注册onFulfilled时的回调函数
catch : 注册onRejected时的回调函数
Promise#catch
只是 promise.then(undefined, onRejected);
方法别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 promise-then-catch -flow.js -------- function taskA ( ) { console .log("Task A" ); } function taskB ( ) { console .log("Task B" ); } function onRejected (error ) { console .log("Catch Error: A or B" , error); } function finalTask ( ) { console .log("Final Task" ); } var promise = Promise .resolve();promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); ---------- Task A Task B Final Task
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 taskA异常 -------- function taskA ( ) { console .log("Task A" ); throw new Error ("throw Error @ Task A" ) } function taskB ( ) { console .log("Task B" ); } function onRejected (error ) { console .log(error); } function finalTask ( ) { console .log("Final Task" ); } var promise = Promise .resolve();promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------- Task A Error : throw Error @ Task AFinal TaskPromise Anti-patterns
Promise#then
不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个promise对象。
点标记法(dot notation) 要求对象的属性必须是有效的标识符(在ECMAScript 3中则不能使用保留字),
中括号标记法(bracket notation) 可以将非合法标识符作为对象的属性名使用
1 2 3 4 5 6 7 var promise = Promise .reject(new Error ("message" ));promise["catch" ](function (error ) { console .error(error); }); -------- Error : message
Promise Anti-patterns
5. Promise.all Promise.all
接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用.then
方法。
传递给 Promise.all
的promise是同时开始、并行执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function timerPromisefy (delay ) { return new Promise (function (resolve ) { setTimeout(function ( ) { resolve(delay); }, delay); }); } var startDate = Date .now();Promise .all([ timerPromisefy(1 ), timerPromisefy(32 ), timerPromisefy(64 ), timerPromisefy(128 ) ]).then(function (values ) { console .log(Date .now() - startDate + 'ms' ); console .log(values); }); -------- 128 ms(及以上)1 ,32 ,64 ,128
6. Promise.race Promise.all
在接收到的所有的对象promise都变为 FulFilled
或者 Rejected
状态之后才会继续进行后面的处理。
Promise.race
只要有一个promise对象进入 FulFilled
或者 Rejected
状态的话,就会继续进行后面的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function timerPromisefy (delay ) { return new Promise (function (resolve ) { setTimeout(function ( ) { resolve(delay); }, delay); }); } Promise .race([ timerPromisefy(1 ), timerPromisefy(32 ), timerPromisefy(64 ), timerPromisefy(128 ) ]).then(function (value ) { console .log(value); }); -------- 1
Promise.race
在第一个promise对象变为Fulfilled
之后,并不会取消其他promise对象的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var winnerPromise = new Promise (function (resolve ) { setTimeout(function ( ) { console .log('this is winner' ); resolve('this is winner' ); }, 4 ); }); var loserPromise = new Promise (function (resolve ) { setTimeout(function ( ) { console .log('this is loser' ); resolve('this is loser' ); }, 1000 ); }); Promise .race([winnerPromise, loserPromise]).then(function (value ) { console .log(value); }); --------- this is winnerthis is winnerthis is loser
5. Promise只能进行异步操作 1 2 3 4 5 6 7 8 9 10 11 12 13 var promise = new Promise (function (resolve ) { console .log("inner promise" ); resolve(42 ); }); promise.then(function (value ) { console .log(value); }); console .log("outer promise" ); ---- inner promise outer promise 42
由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始resolve(42)
被执行。这时候 promise 对象的已经变为确定状态,FulFilled
被设置为了 42 。
但是即使在调用 promise.then
注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。
绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。
对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。
如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。
Effective JavaScript — David Herman
6. 错误处理机制 promise.then(onFulfilled, onRejected)
在 onFulfilled
中发生异常的话,在 onRejected
中是捕获不到这个异常的。
promise.then(onFulfilled).catch(onRejected)
then
中产生的异常能在 .catch
中捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function throwError (value ) { throw new Error (value); } function badMain (onRejected ) { return Promise .resolve(42 ).then(throwError, onRejected); } function goodMain (onRejected ) { return Promise .resolve(42 ).then(throwError).catch(onRejected); } badMain(function ( ) { console .log("BAD" ); }); goodMain(function ( ) { console .log("GOOD" ); }); --------- GOOD
goodMain
的代码则遵循了 throwError→onRejected
的调用流程。 这时候 throwError
中出现异常的话,在会被方法链中的下一个方法,即 .catch
所捕获,进行相应的错误处理。
.then
方法中的onRejected
参数所指定的回调函数,实际上针对的是其promise对象或者之前的promise对象,而不是针对 .then
方法里面指定的第一个参数,即onFulfilled
所指向的对象,这也是 then
和 catch
表现不同的原因。
只用then方法也可以,但意图不够明确
1 Promise.resolve(42).then(throwError).then(null, onRejected);
7. 测试 1 npm install --save-dev mocha
1. 使用done和回调函数测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ------------------------ if (typeof Promise .prototype.done === 'undefined' ) { Promise .prototype.done = function (onFulfilled, onRejected ) { this .then(onFulfilled, onRejected).catch(function (error ) { setTimeout(function ( ) { throw error; }, 0 ); }); }; } var promise = Promise .resolve();promise.done(function ( ) { JSON .parse('this is not json' ); });
done
并不返回promise对象
也就是说,在done
之后不能使用 catch
等方法组成方法链
done
中发生的异常会被直接抛给外面
也就是说,不会进行Promise的错误处理(Error Handling)。done会在函数中跳过错误处理,直接抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 basic-test.js ------------- var assert = require ('power-assert' );describe('Basic Test' , function ( ) { context('When Callback(high-order function)' , function ( ) { it('should use `done` for test' , function (done ) { setTimeout(function ( ) { assert(true ); done(); }, 0 ); }); }); context('When promise object' , function ( ) { it('should use `done` for test?' , function (done ) { var promise = Promise .resolve(1 ); promise.then(function (value ) { assert(value === 1 ); done(); }); }); }); });
通常情况下,assert
失败的时候,会throw一个error, 测试框架会捕获该error,来判断测试失败。
但是,Promise的情况下 .then
绑定的函数执行时发生的error 会被Promise捕获,而测试框架则对此error将会一无所知。
为了处理 assert
失败的情况,我们需要额外添加 .then(done, done);
的代码。 这就要求我们在编写Promise测试时要格外小心,忘了加上上面语句的话,很可能就会写出一个永远不会返回直到超时的测试代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 it("should use `done` for test?" , function (done ) { var promise = Promise .resolve(); promise.then(function (value ) { assert(false ); done(); }); }); --------修改 it("should use `done` for test?" , function (done ) { var promise = Promise .resolve(); promise.then(function (value ) { assert(false ); }).then(done, done); });
2. 对Promise测试 1 2 3 4 5 6 7 8 9 10 var assert = require ('power-assert' );describe('Promise Test' , function ( ) { it('should return a promise object' , function ( ) { var promise = Promise .resolve(1 ); return promise.then(function (value ) { assert(value === 1 ); }); }); });
8. Promise的实现类库 jakearchibald/es6-promise 一个兼容 ES6 Promises 的Polyfill类库。 它基于 RSVP.js 这个兼容 Promises/A+ 的类库, 它只是 RSVP.js 的一个子集,只实现了Promises 规定的 API。
yahoo/ypromise 这是一个独立版本的 YUI 的 Promise Polyfill,具有和 ES6 Promises 的兼容性。 本书的示例代码也都是基于这个 ypromise 的 Polyfill 来在线运行的。
getify/native-promise-only 以作为ES6 Promises的polyfill为目的的类库 它严格按照ES6 Promises的规范设计,没有添加在规范中没有定义的功能。 如果运行环境有原生的Promise支持的话,则优先使用原生的Promise支持。
Promise扩展类库 kriskowal/q 类库 Q 实现了 Promises 和 Deferreds 等规范。 它自2009年开始开发,还提供了面向Node.js的文件IO API Q-IO 等, 是一个在很多场景下都能用得到的类库。
petkaantonov/bluebird 这个类库除了兼容 Promise 规范之外,还扩展了取消promise对象的运行,取得promise的运行进度,以及错误处理的扩展检测等非常丰富的功能,此外它在实现上还在性能问题下了很大的功夫。
9. callback & promise & thenable 1. Web Notification 包装函数(wrapper) 回调风格1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ------------------------ function notifyMessage (message, options, callback ) { if (Notification && Notification.permission === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else if (Notification.requestPermission) { Notification.requestPermission(function (status ) { if (Notification.permission !== status) { Notification.permission = status; } if (status === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else { callback(new Error ('user denied' )); } }); } else { callback(new Error ('doesn\'t support Notification API' )); } } notifyMessage("Hi!" , {}, function (error, notification ) { if (error){ return console .error(error); } console .log(notification); });
2. Web Notification as Promise 返回promise对象的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ---------------------------- function notifyMessage (message, options, callback ) { if (Notification && Notification.permission === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else if (Notification.requestPermission) { Notification.requestPermission(function (status ) { if (Notification.permission !== status) { Notification.permission = status; } if (status === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else { callback(new Error ('user denied' )); } }); } else { callback(new Error ('doesn\'t support Notification API' )); } } function notifyMessageAsPromise (message, options ) { return new Promise (function (resolve, reject ) { notifyMessage(message, options, function (error, notification ) { if (error) { reject(error); } else { resolve(notification); } }); }); } notifyMessageAsPromise("Hi!" ).then(function (notification ) { console .log(notification); }).catch(function (error ) { console .error(error); });
3. Web Notifications As Thenable thenable
就是一个具有 .then
方法的一个对象。
then
方法的参数和 new Promise(function (resolve, reject){})
一样,在确定时执行 resolve
方法,拒绝时调用 reject
方法。
我们可以看出, Promise.resolve(thenable)
通过使用了 thenable
这个promise对象,就能利用Promise功能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 -------------------------- function notifyMessage (message, options, callback ) { if (Notification && Notification.permission === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else if (Notification.requestPermission) { Notification.requestPermission(function (status ) { if (Notification.permission !== status) { Notification.permission = status; } if (status === 'granted' ) { var notification = new Notification(message, options); callback(null , notification); } else { callback(new Error ('user denied' )); } }); } else { callback(new Error ('doesn\'t support Notification API' )); } } function notifyMessageAsThenable (message, options ) { return { 'then' : function (resolve, reject ) { notifyMessage(message, options, function (error, notification ) { if (error) { reject(error); } else { resolve(notification); } }); } }; } Promise .resolve(notifyMessageAsThenable("message" )).then(function (notification ) { console .log(notification); }).catch(function (error ) { console .error(error); });
Thenable
本身并不依赖于Promise功能,但是Promise之外也没有使用Thenable
的方式,所以可以认为Thenable
间接依赖于Promise。
10. Deferred & Promise deferred 介绍
The Deferred object, introduced in jQuery 1.5, is a chainable utility object created by calling the jQuery.Deferred() method. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.
Deferred最初是在Python的 Twisted 框架中被提出来的概念。 在JavaScript领域可以认为它是由 MochiKit.Async 、 dojo/Deferred 等Library引入的。
jQuery.Deferred
JSDeferred
下图为jQuery.Deferred
结构的简化版。当然也有的Deferred
实现并没有内涵Promise。
基于Promise实现Deferred 1 2 3 4 5 6 7 8 9 10 11 12 function Deferred ( ) { this .promise = new Promise (function (resolve, reject ) { this ._resolve = resolve; this ._reject = reject; }.bind(this )); } Deferred.prototype.resolve = function (value ) { this ._resolve.call(this .promise, value); }; Deferred.prototype.reject = function (reason ) { this ._reject.call(this .promise, reason); };
实例 所谓的能对Promise状态进行操作的特权方法,指的就是能对promise对象的状态进行resolve
、reject
等调用的方法,而通常的Promise的话只能在通过构造函数传递的方法之内对promise对象的状态进行操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ----------------- function getURL (URL ) { return new Promise (function (resolve, reject ) { var req = new XMLHttpRequest(); req.open('GET' , URL, true ); req.onload = function ( ) { if (req.status === 200 ) { resolve(req.responseText); } else { reject(new Error (req.statusText)); } }; req.onerror = function ( ) { reject(new Error (req.statusText)); }; req.send(); }); } var URL = "http://httpbin.org/get" ;getURL(URL).then(function onFulfilled (value ) { console .log(value); }).catch(console .error.bind(console ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ---------------- function Deferred ( ) { this .promise = new Promise (function (resolve, reject ) { this ._resolve = resolve; this ._reject = reject; }.bind(this )); } Deferred.prototype.resolve = function (value ) { this ._resolve.call(this .promise, value); }; Deferred.prototype.reject = function (reason ) { this ._reject.call(this .promise, reason); }; function getURL (URL ) { var deferred = new Deferred(); var req = new XMLHttpRequest(); req.open('GET' , URL, true ); req.onload = function ( ) { if (req.status === 200 ) { deferred.resolve(req.responseText); } else { deferred.reject(new Error (req.statusText)); } }; req.onerror = function ( ) { deferred.reject(new Error (req.statusText)); }; req.send(); return deferred.promise; } var URL = "http://httpbin.org/get" ;getURL(URL).then(function onFulfilled (value ) { console .log(value); }).catch(console .error.bind(console ));
Deferred 不需要将代码用Promise括起来
由于没有被嵌套在函数中,可以减少一层缩进
反过来没有Promise里的错误处理逻辑
Promise代表了一个对象 ,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected);Deferred对象表示了一个处理还没有结束的这种事实 ,在它的处理结束的时候,可以通过Promise来取得处理结果。
11. 使用Promise.race来实现超时机制 1 2 3 4 5 6 7 8 ----------------- function delayPromise (ms ) { return new Promise (function (resolve ) { setTimeout(resolve, ms); }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 --------------------------- function delayPromise (ms ) { return new Promise (function (resolve ) { setTimeout(resolve, ms); }); } function timeoutPromise (promise, ms ) { var timeout = delayPromise(ms).then(function ( ) { throw new Error ('Operation timed out after ' + ms + ' ms' ); }); return Promise .race([promise, timeout]); }
函数 timeoutPromise(比较对象promise, ms)
接收两个参数,第一个是需要使用超时机制的promise对象,第二个参数是超时时间,它返回一个由 Promise.race
创建的相互竞争的promise对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function delayPromise (ms ) { return new Promise (function (resolve ) { setTimeout(resolve, ms); }); } function timeoutPromise (promise, ms ) { var timeout = delayPromise(ms).then(function ( ) { throw new Error ('Operation timed out after ' + ms + ' ms' ); }); return Promise .race([promise, timeout]); } var taskPromise = new Promise (function (resolve ) { var delay = Math .random() * 2000 ; setTimeout(function ( ) { resolve(delay + "ms" ); }, delay); }); timeoutPromise(taskPromise, 1000 ).then(function (value ) { console .log("taskPromise在规定时间内结束 : " + value); }).catch(function (error ) { console .log("发生超时" , error); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 ---------------------------- function copyOwnFrom (target, source ) { Object .getOwnPropertyNames(source).forEach(function (propName ) { Object .defineProperty(target, propName, Object .getOwnPropertyDescriptor(source, propName)); }); return target; } function TimeoutError ( ) { var superInstance = Error .apply(null , arguments ); copyOwnFrom(this , superInstance); } TimeoutError.prototype = Object .create(Error .prototype); TimeoutError.prototype.constructor = TimeoutError; function delayPromise (ms ) { return new Promise (function (resolve ) { setTimeout(resolve, ms); }); } function timeoutPromise (promise, ms ) { var timeout = delayPromise(ms).then(function ( ) { return Promise .reject(new TimeoutError('Operation timed out after ' + ms + ' ms' )); }); return Promise .race([promise, timeout]); } function cancelableXHR (URL ) { var req = new XMLHttpRequest(); var promise = new Promise (function (resolve, reject ) { req.open('GET' , URL, true ); req.onload = function ( ) { if (req.status === 200 ) { resolve(req.responseText); } else { reject(new Error (req.statusText)); } }; req.onerror = function ( ) { reject(new Error (req.statusText)); }; req.onabort = function ( ) { reject(new Error ('abort this request' )); }; req.send(); }); var abort = function ( ) { if (req.readyState !== XMLHttpRequest.UNSENT) { req.abort(); } }; return { promise: promise, abort: abort }; } var object = cancelableXHR('http://httpbin.org/get' );timeoutPromise(object.promise, 1000 ).then(function (contents ) { console .log('Contents' , contents); }).catch(function (error ) { if (error instanceof TimeoutError) { object.abort(); return console .log(error); } console .log('XHR Error :' , error); });
通过 cancelableXHR
方法取得包装了XHR的promise对象和取消该XHR请求的方法
在 timeoutPromise
方法中通过 Promise.race
让XHR的包装promise和超时用promise进行竞争。
XHR在超时前返回结果的话
和正常的promise一样,通过 then
返回请求结果
发生超时的时候
12. 易错点 http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/
1. promises vs promises factories 当我们希望执行一个个的执行一个 promises 序列,即类似 Promise.all() 但是并非并行的执行所有 promises。
错误写法(传入 executeSequentially() 的 promises 依然会并行执行):
1 2 3 4 5 6 7 function executeSequentially (promises ) { var result = Promise .resolve(); promises.forEach(function (promise ) { result = result.then(promise); }); return result; }
其根源在于你所希望的,实际上根本不是去执行一个 promises 序列。 依照 promises 规范,一旦一个 promise 被创建,它就被执行了 。 因此你实际上需要的是一个 promise factories 数组。
正确写法:
1 2 3 4 5 6 7 8 9 10 11 function executeSequentially (promiseFactories ) { var result = Promise .resolve(); promiseFactories.forEach(function (promiseFactory ) { result = result.then(promiseFactory); }); return result; } function myPromiseFactory ( ) { return somethingThatCreatesAPromise(); }
一个 promises factory 仅仅是一个可以返回 promise 的函数, 在被执行之前并不会创建 promise。它就像一个 then 函数一样,而实际上,它们就是完全一样的东西。
2. promise依赖 & 命名函数 有时候,一个 promise 会依赖于另一个,但是如果我们希望同时获得这两个 promises 的输出。
错误写法:1 2 3 4 5 getUserByName('nolan' ).then(function (user ) { return getUserAccountById(user.id); }).then(function (userAccount ) { });
正确的金字塔写法:
1 2 3 4 5 getUserByName('nolan' ).then(function (user ) { return getUserAccountById(user.id).then(function (userAccount ) { }); });
避免缩进问题,将函数抽离到一个命名函数中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function onGetUserAndUserAccount (user, userAccount ) { return doSomething(user, userAccount); } function onGetUser (user ) { return getUserAccountById(user.id).then(function (userAccount ) { return onGetUserAndUserAccount(user, userAccount); }); } getUserByName('nolan' ) .then(onGetUser) .then(function ( ) { });
3. promise穿透 1 2 3 4 5 6 Promise .resolve('foo' ).then(Promise .resolve('bar' )).then(function (result ) { console .log(result); }); --------------- result: foo
发生这个的原因是如果你像 then()
传递的并非是一个函数(比如 promise),它实际上会将其解释为 then(null)
,这就会导致前一个 promise 的结果会穿透下面。1 2 3 Promise .resolve('foo' ).then(null ).then(function (result ) { console .log(result); });
then()
是期望获取一个函数。
正确写法:
1 2 3 4 5 6 7 8 Promise .resolve('foo' ).then(function ( ) { return Promise .resolve('bar' ); }).then(function (result ) { console .log(result); }); --------------- result: bar
13. 经典问题 Puzzle #1 1 2 3 doSomething().then(function ( ) { return doSomethingElse(); }).then(finalHandler);
Answer:
1 2 3 4 5 6 doSomething |-----------------| doSomethingElse(undefined) |------------------| finalHandler(resultOfDoSomethingElse) |------------------|
Puzzle #2 1 2 3 doSomething().then(function ( ) { doSomethingElse(); }).then(finalHandler);
Answer:
1 2 3 4 5 6 doSomething |-----------------| doSomethingElse(undefined) |------------------| finalHandler(undefined) |------------------|
Puzzle #3 1 2 doSomething().then(doSomethingElse()) .then(finalHandler);
Answer:
1 2 3 4 5 6 doSomething |-----------------| doSomethingElse(undefined) |---------------------------------| finalHandler(resultOfDoSomething) |------------------|
Puzzle #4 1 2 doSomething().then(doSomethingElse) .then(finalHandler);
Answer:
1 2 3 4 5 6 doSomething |-----------------| doSomethingElse(resultOfDoSomething) |------------------| finalHandler(resultOfDoSomethingElse) |------------------|
5. 深入阅读 Promise 必知必会(十道题)
深入 Promise(一)——Promise 实现详解
深入 Promise(二)——进击的 Promise
深入 Promise(三)——命名 Promise